package main

import (
	"encoding/json"
	"encoding/xml"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/linclaus/stock/pkg/db"
	"github.com/linclaus/stock/pkg/weixin/model"
	"github.com/linclaus/stock/pkg/weixin/utils"
)

var userNode = map[string]model.Node{}
var token string
var secret string

func main() {
	var port string
	flag.StringVar(&port, "port", ":80", "The address of weixin APP")
	flag.StringVar(&db.MYSQL_CON, "mysqlCon", "root:123456@tcp(127.0.0.1:3306)/stock?charset=utf8", "Mysql Connetction String")
	flag.StringVar(&utils.STOCK_HOST, "stockHost", "http://stock:8080", "The address of stock APP")
	flag.StringVar(&utils.HOST, "host", "http://localhost", "The host of Stock-weixin")
	flag.IntVar(&utils.DefaultMaxStrategiesNum, "defaultMaxStrategiesNum", 3, "The default max num of straties")
	flag.StringVar(&token, "token", "xiaobaikangu123", "The token of weixin APP")
	flag.StringVar(&secret, "secret", "6a2d9fe4c7fa8b545fd6e18b2b44fd46", "The secret of weixin APP")
	flag.Parse()
	db.InitDB()
	r := mux.NewRouter()
	r.HandleFunc("/wx", WeixinGet).Methods("GET")
	r.HandleFunc("/wx", WeixinPost).Methods("POST")
	r.HandleFunc("/alert", AlertWebhook).Methods("POST")
	r.HandleFunc("/image/{code}", GetStockImage).Methods("GET")
	log.Printf("Start Stock-Weixin on port: %s\n", port)
	log.Fatal(http.ListenAndServe(port, r))
}

func WeixinGet(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	log.Printf("qurey: %s\n", query)
	timestamp, _ := query["timestamp"]
	nonce, _ := query["nonce"]
	signature, _ := query["signature"]
	echostr, _ := query["echostr"]
	if utils.ValidateSignature(token, timestamp[0], nonce[0], signature[0]) {
		w.Write([]byte(echostr[0]))
	} else {
		w.Write([]byte("error"))
	}
}

func WeixinPost(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	log.Printf("qurey: %s\n", query)
	timestamp, _ := query["timestamp"]
	nonce, _ := query["nonce"]
	signature, _ := query["signature"]
	mySecret, _ := query["secret"]
	openid, _ := query["openid"]

	if !utils.ValidateSignature(token, timestamp[0], nonce[0], signature[0]) && mySecret[0] != secret {
		log.Printf("openid: %s valid failed\n", openid[0])
		w.Write([]byte("error"))
	}
	log.Println("Validation success")

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Failed to read payload: %s\n", err)
		http.Error(w, fmt.Sprintf("Failed to read payload: %s", err), http.StatusBadRequest)
		return
	}
	reqMsg := model.WeixinRequestMsg{}
	xml.Unmarshal(body, &reqMsg)
	log.Printf("request body: %s", body)
	userId := reqMsg.FromUserName
	switch reqMsg.MsgType {
	case "text":
		input := reqMsg.Content
		output := []string{}
		if originNode, ok := userNode[userId]; ok {
			targetNode := originNode.Handler(input, userId, &output)
			userNode[userId] = targetNode
			fmt.Printf("originNode: %s, targetNode: %s\n", originNode.Name, targetNode.Name)
		} else {
			userNode[userId] = model.RootNode
			fmt.Println("node not found")
		}
		fmt.Printf("Message: %s\n", userNode[userId].CommandList)
		resMsg := GenerateTextMsg(reqMsg.FromUserName, reqMsg.ToUserName, "text", strings.Join(append(output, userNode[userId].CommandList...), "\n"))
		resBytes, err := xml.Marshal(resMsg)
		if err != nil {
			log.Printf("marshal error for reason: %s", err.Error())
			w.Write([]byte("success"))
		}
		log.Printf("response body: %s", string(resBytes))
		w.Write(resBytes)

	case "event":
		switch reqMsg.Event {
		case "subscribe":
			resMsg := GenerateTextMsg(reqMsg.FromUserName, reqMsg.ToUserName, "text", "欢迎关注小白看股，可以试试与我交互哦。")
			resBytes, err := xml.Marshal(resMsg)
			if err != nil {
				log.Printf("marshal error for reason: %s", err.Error())
				w.Write([]byte("success"))
			}
			log.Printf("response body: %s", string(resBytes))
			w.Write(resBytes)
		case "unsubscribe":
			log.Printf("%s unsubscribe event\n", reqMsg.FromUserName)
			output := []string{}
			model.StrategyDeleteNode.Handler("all", userId, &output)
			delete(userNode, userId)
		}
	default:
		w.Write([]byte("success"))
	}
}

func AlertWebhook(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Failed to read payload: %s\n", err)
		http.Error(w, fmt.Sprintf("Failed to read payload: %s", err), http.StatusBadRequest)
		return
	}
	log.Printf("request body: %s", body)
	alerts := map[string]interface{}{}
	json.Unmarshal(body, &alerts)
	if strategyId, ok := alerts["receiver"]; ok {
		db.UserStrategyMgr.UpdateLastAlertTime(strategyId.(string), time.Now())
	}
	if commonLabels, ok := alerts["commonLabels"]; ok {
		if code, ok := commonLabels.(map[string]interface{})["code"]; ok {
			url := fmt.Sprintf("%s/dashboard/%s/image", utils.STOCK_HOST, code.(string))
			var client = http.Client{}
			req, err := http.NewRequest("POST", url, nil)
			req.Header.Add("Accept", "application/json")
			resp, err := client.Do(req)
			if err != nil {
				fmt.Printf("http.Post -> %v\n", err)
				return
			}
			defer resp.Body.Close()
			fmt.Printf("save image responseCode: %d\n", resp.StatusCode)
		}
	}
}

func GetStockImage(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	url := fmt.Sprintf("%s/dashboard/%s/image", utils.STOCK_HOST, vars["code"])
	var client = http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Accept", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("http.Get -> %v\n", err)
		return
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("ioutil.ReadAll -> %v\n", err)
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		w.Write([]byte("快照未准备好，请稍后再试"))
		http.NotFoundHandler().ServeHTTP(w, r)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	w.Write(data)
}

func GenerateTextMsg(to, from, msgType, msgText string) model.WeixinResponseTextMsg {
	return model.WeixinResponseTextMsg{
		ToUserName: struct {
			Value string `xml:",innerxml"`
		}{fmt.Sprintf("<![CDATA[%s]]>", to)},
		FromUserName: struct {
			Value string `xml:",innerxml"`
		}{fmt.Sprintf("<![CDATA[%s]]>", from)},
		CreateTime: time.Now().Nanosecond(),
		MsgType: struct {
			Value string `xml:",innerxml"`
		}{fmt.Sprintf("<![CDATA[%s]]>", msgType)},
		Content: struct {
			Value string `xml:",innerxml"`
		}{fmt.Sprintf("<![CDATA[%s]]>", msgText)},
	}
}
